package com.zhj.edu.filter;

import com.alibaba.fastjson.JSON;
import com.zhj.edu.bo.Account;
import com.zhj.edu.bo.SecurityUser;
import com.zhj.edu.context.Context;
import com.zhj.edu.manager.RedisManager;
import com.zhj.edu.result.R;
import com.zhj.edu.result.ResponseCode;
import com.zhj.edu.service.ITokenService;
import com.zhj.edu.utils.HttpResponseUtils;
import com.zhj.edu.vo.LoginVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 重写登录的过滤器
 */
@Slf4j
public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    private ITokenService tokenService;

    private RedisManager redisManager;

    private Integer expire;

    /**
     * 这里通过构造器注入
     * @param authenticationManager
     */
    public LoginFilter(AuthenticationManager authenticationManager, ITokenService tokenService, RedisManager redisManager, Integer expire) {
        this.authenticationManager = authenticationManager;
        this.tokenService = tokenService;
        this.redisManager = redisManager;
        this.expire = expire;
        //这里还需要设置登录的地址和请求方法,表示的是在请求这个地址时，才能运行这个过滤器
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/iotserver/user/login", HttpMethod.POST.name()));
    }

    /**
     * 登录的方法
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //首先要封装请求参数回来
        LoginVo loginVo = getAccountInfo(request);
        //然后将用户和密码信息以及type封装成一个对象 - UsernameAndPasswordToken对象
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(JSON.toJSONString(loginVo), loginVo.getPassword());
        //接下来就可以调用AuthenticationManager来完成登录了
        return authenticationManager.authenticate(usernamePasswordAuthenticationToken);
    }

    /**
     * 从请求中获取账户信息
     * @param request
     * @return
     */
    private LoginVo getAccountInfo(HttpServletRequest request) {
        //get获取数据的方式：getParameter 和 getQueryString
        //post获取数据的方式：getParameter 和 IO流
        //post传输 设置编码格式
        try {
            request.setCharacterEncoding("UTF-8");
            ServletInputStream inputStream = request.getInputStream();
            byte[] buf = new byte[8192];
            //用户名密码比较少，可以一次性读完
            int read = inputStream.read(buf);
            //将获取到的数据转换为字符串
            String val = new String(buf, 0, read, "UTF-8");
            //将这个值转换为 LoginVo 对象 - fastjson
            LoginVo loginVo = JSON.parseObject(val, LoginVo.class);
            return loginVo;
        } catch (Exception e) {
            throw new RuntimeException("获取前端传输参数有误...");
        }
    }

    /**
     * 登录成功的回调
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        //如果登录成功就会执行到这里来
        //这里生成token 生成token之前首先拿到id和名字
        SecurityUser securityUser = (SecurityUser) authResult.getPrincipal();
        //权限和角色的集合
        List<String> roleAndPerms = securityUser.getRoleAndPerms();
        //取出id和用户名
        Account account = securityUser.getAccount();
        String id = account.getId();
        String username = account.getUsername();
        Integer type = account.getType();
        //接下来就可以生成token
        String token = tokenService.createToken(id, username);
        log.info("生成的token信息是:"+token);
        //方法一：将权限信息和角色信息存储到内存中
        //Context.getRoleAndPermMap().put(token,roleAndPerms);

        //方法二：将权限和角色信息放到redis中
        redisManager.addString(token, JSON.toJSONString(roleAndPerms), this.expire, TimeUnit.DAYS);

        //最后将token信息和id以及username的信息返回给客户端
        R r = R.ok().data("token", token).data("userId", id).data("username", username);
        //然后调用响应的方法返回给客户端
        HttpResponseUtils.sendResponse(response,r);
    }

    /**
     * 登录失败的回调
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        //如果登录失败就会执行到这里来
        R error = R.error(ResponseCode.LOGIN_EXCEPTION);
        HttpResponseUtils.sendResponse(response,error);
    }
}
